package top.kaoshanji.ch03;

import com.google.common.io.Resources;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.net.URL;
import java.util.Map;

/**
 * 代码清单 ch03-01：DOM解析示例(3.3节、将一个XML文档转换成JSON格式)
 * author kaoshanji
 * ^_^
 * create time 2024/12/7 10:10
 */
public class JSONConverter {

    private static final Logger logger = LogManager.getLogger(JSONConverter.class);

    static final String FILE_NAME = "ch03/server.xml";

    public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException {
        logger.info("...代码清单 ch03-01：DOM解析示例(3.3节、将一个XML文档转换成JSON格式)...");

        URL url = Resources.getResource(FILE_NAME);

        // 看到代码的是在此之前改动不知道多少次了..早已不是当初的摸样，现在看到的是逻辑清晰，各种封装
        // 理解代码在了解逻辑之后需要找到关键节点

        // 第一步：首先需要 DocumentBuilder 对象
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();

        // 第二步：获得 Document 对象，是XML文档的树型结构在内存中的表示方式
        Document doc = builder.parse(url.openStream());

        // 第三步：获得根元素
        Element root = doc.getDocumentElement();

        // 触发--解析
        System.out.println(convert(root, 0));

    }

    // 中心流程..处理各种节点类型
    public static StringBuilder convert(Node node, int level) {

        if (node instanceof Element) {
            return elementObject((Element) node, level);
        } else if (node instanceof CharacterData) {
            return characterString((CharacterData) node, level);
        } else {
            return pad(new StringBuilder(), level).append(jsonEscape(node.getClass().getName()));
        }

    }

    private static Map<Character, String> replacements = Map.of('\b', "\\b", '\f', "\\f",
            '\n', "\\n", '\r', "\\r", '\t', "\\t", '"', "\\\"", '\\', "\\\\");


    private static StringBuilder jsonEscape(String str) {

        var result = new StringBuilder("\"");
        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            String replacement = replacements.get(ch);
            if (replacement == null) {
                result.append(ch);
            } else {
                result.append(replacement);
            }
        }
        result.append("\"");
        return result;

    }

    private static StringBuilder characterString(CharacterData node, int level) {

        var result = new StringBuilder();
        StringBuilder data = jsonEscape(node.getData());
        if (node instanceof Comment) {
            data.insert(1, "Comment: ");
        }
        pad(result, level).append(data);
        return result;

    }

    private static StringBuilder elementObject(Element elem, int level) {

        var result = new StringBuilder();
        pad(result, level).append("{\n");
        pad(result, level + 1).append("\"name\": ");
        result.append(jsonEscape(elem.getTagName()));
        NamedNodeMap attrs = elem.getAttributes();
        if (attrs.getLength() > 0) {
            pad(result.append(",\n"), level + 1).append("\"attributes\": ");
            result.append(attributeObject(attrs));
        }
        NodeList children = elem.getChildNodes();
        if (children.getLength() > 0) {
            pad(result.append(",\n"), level + 1).append("\"children\": [\n");
            for (int i = 0; i < children.getLength(); i++)
            {
                if (i > 0) result.append(",\n");
                result.append(convert(children.item(i), level + 2));
            }
            result.append("\n");
            pad(result, level + 1).append("]\n");
        }
        pad(result, level).append("}");
        return result;

    }

    private static StringBuilder pad(StringBuilder builder, int level) {

        for (int i = 0; i < level; i++) {
            builder.append("  ");
        }
        return builder;
    }

    private static StringBuilder attributeObject(NamedNodeMap attrs) {

        var result = new StringBuilder("{");
        for (int i = 0; i < attrs.getLength(); i++) {
            if (i > 0) {
                result.append(", ");
            }
            result.append(jsonEscape(attrs.item(i).getNodeName()));
            result.append(": ");
            result.append(jsonEscape(attrs.item(i).getNodeValue()));
        }
        result.append("}");
        return result;
    }

}
